先前文章的重點放在Django對於資料庫的ORM(Object Relational Mapping),後台應用,還有Django REST framework(DRF)等API操作。這些主要都是單純的數據更新、單向的服務器推送需求。但是如果想要用Django進行其他的Web應用,我們需要更好的解決方案與應用
在這個系列文章中,我們會透過DocuMind這個專案來探討Django在面對需要非同步、實時雙向通訊以及長時間任務下的情境,應該如何來進行實踐
今日重點:
來為大家介紹這個文章系列的主角:DocuMind,取名自Document與Mind的結合
這個專案最主要的功能就是個人的知識庫,具有以下功能:
撇除轉換成向量資料,跟判斷用戶訊息與文庫的相關程度之外,我們需要來看一下我們需要克服哪些技術面的需求:
綜上所述,我們勢必要引入一些新技術了!
不過在開始建立專案之前,我們也得思考這次的專案是否需要用到其他方式來啟動伺服器
在目前爲止,我們單純的使用Django提供的runserver來作為快速開發的後端伺服器,但是為了滿足更多的需求,我們需要進一部探討如果要建立起Web應用,還有哪些啟動專案的方式
這邊來深入了解 Django runserver、WSGI 和 ASGI 這三種不同的伺服器啟動方式,幫助理解它們的區別、優缺點以及適用場景
最快速,且適合開發中除錯的啟動方式
特點:
--nothreading
來變成單線程缺點:
這讓我想到初學時還妄想可以在背景執行runserver然後不用進行部署XD 對於程式小白來說部署真的是惡夢
生產環境中的標準接口
特點:
缺點:
uWSGI的設定檔配置如下(不含所有常見配置):
[uwsgi]
socket = 127.0.0.1:xxxx
processes = 2 # 設定多少進程
threads = 4 # 設定多少進程
master = true
vacuum = true
buffer-size=65536
pidfile=uwsgi.pid
uid=1000
gid=1000
max-requests = 5000 # 限制每個worker處理5000個請求後重啟 防止記憶體泄露
reload-on-rss = 300 # 當worker使用超過350MB記憶體時重啟
支持非同步處理的新一代接口標準
特點:
缺點:
Uvicorn的啟動文檔範例如下:
# 綁定的 IP 和端口
bind = "0.0.0.0:8000"
# 工作進程數
workers = 3
# 使用支持 ASGI 的 worker 類
worker_class = "uvicorn.workers.UvicornWorker"
# WebSocket 相關設置
timeout = 60
keepalive = 65
graceful_timeout = 300
max_requests = 1000
max_requests_jitter = 1000
# 根據環境變數動態設置工作進程數
import os
workers = int(os.environ.get("GUNICORN_WORKERS", "3"))
我們可以用更貼近生活的例子來說明:
想像一下這是一家 24/7 營業的小咖啡館
優點:
缺點:
這是一家連鎖咖啡店
缺點:
這是一家使用最新科技的現代化咖啡館
缺點:
可能會有疑問?既然WSGI都能配置多線程,那這樣怎麼不好處理高併發?除了不支持WebSocket外,ASGI跟WSGI還差在哪裡?
我們還是可以拿這個咖啡廳當舉例:
因為python有具備GIL(全局鎖)的特性,所以即使是多線程,一次能站在櫃檯的工作人員只有一人。並且因為線程的特性,即使知道這是只要等咖啡滴完就能泡好的咖啡,咖啡師也會在那邊等咖啡滴完而不去做其他事情。但是在ASGI使用協程的特性,所以即使還是有GIL的限制,每一個工作人員都能好好的填滿所有的工作時間,不會有乾等的狀況發生
不過使用uWSGI等服務,還是能夠有效的緩解乾等的狀況,會有Master(店經理)來指揮那些只會乾等的咖啡師,在一些I/O密集型,會釋放GIL的任務中,uWSGI的確很夠用。但是多線程之間的切換還是比協程耗資源,同時使用協程不用像多線程那樣,每一個任務(每一杯咖啡)都要分一個線程(咖啡師)
經過上面的比較下來,顯然根據我們DocuMind專案的需求,使用ASGI來啟動專案是更適合的選項!
當然這是指最後階段,開發過程中還是使用Django runserver來進行開發
專案的程式碼:https://github.com/class83108/DocuMind
而今天的範例程式碼則在分支中:https://github.com/class83108/DocuMind/tree/init
目前專案只完成到celery的部分,所以README等都還不會是最終版本。如果最後看完系列文章,覺得這個專案很有趣的話,請不要吝嗇給予星星喔!
# 建立專案
poetry new DocuMind
cd DocuMind
poetry add Django==4.2
poetry add psycopg2-binary
poetry add load-dotenv
poetry add redis
poetry add django-redis
# 啟動虛擬環境
poetry shell
# 建立專案
django-admin startproject documind
在與manage.py
同級目錄建立.env,並配置相對應的參數
# .env
DB_HOST=127.0.0.1
DB_USER=<DB_USER>
DB_PWD=<DB_PWD>
DB_NAME=<DB_NAME>
SECRET_KEY='<your key>'
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=<REDIS_PASSWORD>
DEBUG=true
# settings.py
from dotenv import load_dotenv
import os
ENV_FILE_PATH = BASE_DIR / ".env"
# 加载 .env 文件
load_dotenv(ENV_FILE_PATH)
# postgreSQL 相關設定
DB_HOST = os.getenv("DB_HOST")
DB_USER = os.getenv("DB_USER")
DB_PWD = os.getenv("DB_PWD")
DB_NAME = os.getenv("DB_NAME")
# REDIS 相關
REDIS_HOST = os.getenv("REDIS_HOST")
REDIS_PORT = os.getenv("REDIS_PORT")
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD")
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates/")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": DB_NAME,
"USER": DB_USER,
"PASSWORD": DB_PWD,
"HOST": DB_HOST,
"PORT": "5432",
},
}
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
STATIC_URL = "static/"
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads")
MEDIA_URL = "/media/"
python3 manage.py startapp articles
# articles.models.py
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Article(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
# settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"articles",
]
python3 manage.py makemigrations
python3 manage.py migrate
今天的專案就先告一個段落,明天會開始建立相關的核心功能
我們今天了解了幾種啟動Django專案的方式:
最後開始建構我們這次有趣的專案DocuMind,明天準備建立專案最重要的幾個功能: